<template>
  <span>
    <transition
      :name="transition"
      @after-enter="handleAfterEnter"
      @after-leave="handleAfterLeave">
      <div
        class="el-popover el-popper"
        :class="[popperClass, content && 'el-popover--plain']"
        ref="popper"
        v-show="!disabled && showPopper"
        :style="{ width: width + 'px' }"
        role="tooltip"
        :id="tooltipId"
        :aria-hidden="(disabled || !showPopper) ? 'true' : 'false'"
      >
        <div class="el-popover__title" v-if="title" v-text="title"></div>
        <slot>{{ content }}</slot>
      </div>
    </transition>
    <slot name="reference"></slot>
  </span>
</template>
<script>
import Popper from 'element-ui/src/utils/vue-popper'
import { on, off } from 'element-ui/src/utils/dom'
import { addClass, removeClass } from 'element-ui/src/utils/dom'
import { generateId } from 'element-ui/src/utils/util'

export default {
    name: 'ClPopover',

    mixins: [Popper],

    props: {
        trigger: {
            type: String,
            default: 'click',
            validator: value => ['click', 'focus', 'hover', 'manual', 'hover-click'].indexOf(value) > -1
        },
        openDelay: {
            type: Number,
            default: 0
        },
        closeDelay: {
            type: Number,
            default: 200
        },
        title: String,
        disabled: Boolean,
        content: String,
        reference: {},
        popperClass: String,
        width: {},
        visibleArrow: {
            default: true
        },
        arrowOffset: {
            type: Number,
            default: 0
        },
        transition: {
            type: String,
            default: 'fade-in-linear'
        },
        tabindex: {
            type: Number,
            default: 0
        },
        disableDocumentClick: Boolean
    },

    computed: {
        tooltipId() {
            return `el-popover-${generateId()}`
        }
    },
    watch: {
        showPopper(val) {
            if (this.disabled) {
                return
            }
            val ? this.$emit('show') : this.$emit('hide')
        },
        disableDocumentClick(val) {
            if (this.trigger === 'click') {
                if (val) {
                  off(document, 'click', this.handleDocumentClick)
                } else {
                  setTimeout(() => {
                    on(document, 'click', this.handleDocumentClick)
                  }, 50)
                }
            }
        }
    },

    mounted() {
        let reference = this.referenceElm = this.reference || this.$refs.reference
        const popper = this.popper || this.$refs.popper

        if (!reference && this.$slots.reference && this.$slots.reference[0]) {
            reference = this.referenceElm = this.$slots.reference[0].elm
        }
        // 可访问性
        if (reference) {
            addClass(reference, 'el-popover__reference')
            reference.setAttribute('aria-describedby', this.tooltipId)
            reference.setAttribute('tabindex', this.tabindex) // tab序列
            popper.setAttribute('tabindex', 0)

            if (this.trigger !== 'click') {
                on(reference, 'focusin', () => {
                    this.handleFocus()
                    const instance = reference.__vue__
                    if (instance && typeof instance.focus === 'function') {
                        instance.focus()
                    }
                })
                on(popper, 'focusin', this.handleFocus)
                on(reference, 'focusout', this.handleBlur)
                on(popper, 'focusout', this.handleBlur)
            }
            on(reference, 'keydown', this.handleKeydown)
            on(reference, 'click', this.handleClick)
        }
        if (this.trigger === 'click') {
            on(reference, 'click', this.doToggle)
            on(document, 'click', this.handleDocumentClick)
        } else if (this.trigger === 'hover') {
            on(reference, 'mouseenter', this.handleMouseEnter)
            on(popper, 'mouseenter', this.handleMouseEnter)
            on(reference, 'mouseleave', this.handleMouseLeave)
            on(popper, 'mouseleave', this.handleMouseLeave)
        } else if (this.trigger === 'hover-click') {
            this.$bus.$on('hover-click-show', () => {
                this.handleMouseLeave()
            })
            on(reference, 'mouseenter', this.handleMouseEnter)
            on(popper, 'mouseenter', this.handleMouseEnter)
            on(document, 'click', this.handleDocumentClick)
        } else if (this.trigger === 'focus') {
            if (this.tabindex < 0) {
                console.warn('[Element Warn][Popover]a negative taindex means that the element cannot be focused by tab key')
            }
            if (reference.querySelector('input, textarea')) {
                on(reference, 'focusin', this.doShow)
                on(reference, 'focusout', this.doClose)
            } else {
                on(reference, 'mousedown', this.doShow)
                on(reference, 'mouseup', this.doClose)
            }
        }
    },

    beforeDestroy() {
        this.cleanup()
    },

    deactivated() {
        this.cleanup()
    },

    methods: {
        doToggle() {
            this.showPopper = !this.showPopper
        },
        doShow() {
            this.showPopper = true
        },
        doClose() {
            this.showPopper = false
        },
        handleFocus() {
            addClass(this.referenceElm, 'focusing')
            if (this.trigger === 'click' || this.trigger === 'focus') this.showPopper = true
        },
        handleClick() {
            removeClass(this.referenceElm, 'focusing')
        },
        handleBlur() {
            removeClass(this.referenceElm, 'focusing')
            if (this.trigger === 'click' || this.trigger === 'focus') this.showPopper = false
        },
        handleMouseEnter() {
            this.$bus.$emit('hover-click-show')
            clearTimeout(this._timer)
            if (this.openDelay) {
                this._timer = setTimeout(() => {
                    this.showPopper = true
                }, this.openDelay)
            } else {
                this.showPopper = true
            }
        },
        handleKeydown(ev) {
            if (ev.keyCode === 27 && this.trigger !== 'manual') { // esc
                this.doClose()
            }
        },
        handleMouseLeave() {
            clearTimeout(this._timer)
            if (this.closeDelay) {
                this._timer = setTimeout(() => {
                    this.showPopper = false
                }, this.closeDelay)
            } else {
                this.showPopper = false
            }
        },
        handleDocumentClick(e) {
            let reference = this.reference || this.$refs.reference
            const popper = this.popper || this.$refs.popper

            if (!reference && this.$slots.reference && this.$slots.reference[0]) {
                reference = this.referenceElm = this.$slots.reference[0].elm
            }
            if (!this.$el ||
              !reference ||
              this.$el.contains(e.target) ||
              reference.contains(e.target) ||
              !popper ||
              popper.contains(e.target)) return
            this.showPopper = false
        },
        handleAfterEnter() {
            this.$emit('after-enter')
        },
        handleAfterLeave() {
            this.$emit('after-leave')
            this.doDestroy()
        },
        cleanup() {
            if (this.openDelay || this.closeDelay) {
                clearTimeout(this._timer)
            }
        }
    },

    destroyed() {
        const reference = this.reference

        off(reference, 'click', this.doToggle)
        off(reference, 'mouseup', this.doClose)
        off(reference, 'mousedown', this.doShow)
        off(reference, 'focusin', this.doShow)
        off(reference, 'focusout', this.doClose)
        off(reference, 'mousedown', this.doShow)
        off(reference, 'mouseup', this.doClose)
        off(reference, 'mouseleave', this.handleMouseLeave)
        off(reference, 'mouseenter', this.handleMouseEnter)
        off(document, 'click', this.handleDocumentClick)

        this.$bus.$off('hover-click-show')
    }
}
</script>
